package generatore;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.Vector;
import linguaggio.AttributeType;
import linguaggio.Grammatica;
import linguaggio.NonTerminale;
import linguaggio.Produzione;
import linguaggio.ProduzioneErrore;
import linguaggio.Token;

/**
 * la classe permette la generazione, tramite file .lex ed il file .y,
 * rispettivamente di scanner e parser.
 * @author Giovanni Cammarata
 */
public class Generatore {
    public static String filenameStampoLex ="config/fileStampoLex";
    public static String filenameStampoYacc ="config/fileStampoYacc";
    
    private LinkedList<NonTerminale> nonTerminali;
    private String yaccFilename;
    private String lexFilename;
    private LinkedList<Token> tokens;
    private String rootLALR = "generatoreLALR";
    private String parserClassName = "Parser";
    private String parserValClassName = "ParserVal";
    private Grammatica grammatica;
    
    private Utility.OStype OS;

    public Generatore(Grammatica grammatica, String yaccFilename, String lexFilename) {
        this.grammatica = grammatica;
        this.nonTerminali = grammatica.getNonTerminali();
        this.tokens = grammatica.getTerminali();
        OS = Utility.selezionaOS();
        this.yaccFilename = "src/"+rootLALR+"/"+yaccFilename;
        this.lexFilename = "src/"+rootLALR+"/"+lexFilename;
        
        
        System.out.println("Genero : "+this.yaccFilename);
        System.out.println("Genero : "+this.lexFilename);
    }
    
    
    

    /**
     * Il metodo permette la generazione del Parser java.
     * Richiama il PB dal metodo statico di Utility.
     */
    public String generaParser() {
        
        if(!generaFileYacc())
            return "Errore generazione file yacc";
        
        
        ProcessBuilder pb = Utility.pbYacc(yaccFilename, OS,rootLALR);  //Richiamo il metodo che ritorna il Process Builder in base al SO.
        try {
            Process p = pb.start();
            
            InputStream inStream = p.getInputStream();
           InputStream errStream = p.getErrorStream();
           BufferedReader reader = null;
           String line = null;

           String output="";     //Risultato della generazione
           reader = new BufferedReader( new InputStreamReader(inStream));
           while( (line = reader.readLine()) != null){   
                output =output +"\n"+line;     
                System.out.println(line);
           }
           
           
            /**
                    * Stampo eventuali errori.
                    */
           output="";     //Risultato della generazione
           boolean errore = false;
                   reader = new BufferedReader( new InputStreamReader(errStream));
                   while( (line = reader.readLine()) != null){
                        System.out.println(line);
                        output =output +"\n"+line;
                        System.out.println(line);
                        errore = true;
                   }
                   
           reader.close();
           
           //System.out.println("ERRORE " +errore);
           
           ManipolaFile.copiaFile(parserClassName+".java", "src/"+rootLALR+"/"+parserClassName+".java", false);
           ManipolaFile.cancellaFile(parserClassName+".java");
           
           ManipolaFile.copiaFile(parserValClassName+".java", "src/"+rootLALR+"/"+parserValClassName+".java", false);
           ManipolaFile.cancellaFile(parserValClassName+".java");
           
           if(errore)
               return output;
           else
               return null;
            
        } catch (IOException ex) {
            
            return "Errore generazione file yacc";
        }
    }
    
    
    
    /**
     * Il metodo permette la generazione dello Scanner java.
     * Richiama il PB dal metodo statico di Utility.
     */
    public String generaScanner() {
                
        if(!generaFileLex())
            return "Errore generazione file lex";
        
        ProcessBuilder pb = Utility.pbLex(lexFilename, OS);           //Richiamo il metodo che ritorna il Process Builder in base al SO.
        
        try {
            Process p = pb.start();
            
            InputStream inStream = p.getInputStream();
           InputStream errStream = p.getErrorStream();
           BufferedReader reader = null;
           String line = null;

           
           String output="";     //Risultato della generazione
           reader = new BufferedReader( new InputStreamReader(inStream));
           while( (line = reader.readLine()) != null){   
                output =output +"\n"+line;     
                System.out.println(line);
           }
           
           /**
                    * Stampo eventuali errori.
                    */
           output="";     //Risultato della generazione
           boolean errore = false;
                   reader = new BufferedReader( new InputStreamReader(errStream));
                   while( (line = reader.readLine()) != null){
                        System.out.println(line);
                        output =output +"\n"+line;
                        System.out.println(line);
                        errore = true;
                   }
                   
           reader.close();
           
           //System.out.println("ERRORE " +errore);
           
           if(errore)
               return output;
           else
               return null;
           
        } catch (IOException ex) {
            return "Errore generazione file lex";
        }
    }
    
    /**
     * Permette la generazione del file che andra' in pasto a lex.
     * @return true se il file e' stato generato correttamente.
     */
    private boolean generaFileLex(){
        File lexFile;       //nuovo file che verra' creato
        FileWriter fw = null;
        
        //Copio la struttura del file nel nuovo file di unput per lex.
        if(ManipolaFile.copiaFile(filenameStampoLex, lexFilename,false)) {
            
            //Inizio la costruzione del nuovo file .lex .
            lexFile = new File(lexFilename);
            try {
                fw = new FileWriter(lexFile,true);  //apro il file in append per aggiungere le nuove cose
                
                fw.write("\n\n");
                fw.flush();
                
                //PRIMA PARTE
                String line = null;
                Token tk = null;
                Iterator it = tokens.iterator();
                while(it.hasNext()) {
                    tk = (Token)it.next();
                    line = "\n"+tk.getName()+" = "+tk.getExpression();
                    fw.write(line);
                    fw.flush();
                }
                
                fw.write("\n\n%%\n\n");
                fw.flush();
                
                
                //SECONDA PARTE
                it = tokens.iterator();
                while(it.hasNext()) {
                    tk = (Token)it.next();
                    line = "\n{"+tk.getName()+"}    { "+Utility.creaToken(tk, parserValClassName);
                    if(tk.getThisTokenType() != AttributeType.tokenType.ignora)
                        line+="\n\t\t\t\treturn "+parserClassName+"."+tk.getName()+";}";
                    else
                        line+="}";
                    
                    fw.write(line);
                    fw.flush();
                }
                
                
                fw.close();
                return true;
            } 
            catch (IOException ex) {
                return false;
            }
            
        }
        
        return false;
    }
    
    
    /**
     * Permette la generazione del file che andra' in pasto a yacc.
     * Inserisce quindi i token considerando anche gli eventuali tipi selezionati apputno per il token,
     * quali ad esempio <dval> per float, <sval> per stringa, <ival> per intero, <obj> per un oggetto.
     * E' possibile anche non specificare il tipo di token.
     * Vado a ricavare i NON TERMINALI (parte sinistra) dalal HashMap ed aggiungo le produzioni in OR,
     * con eventuali parti destre.
     * Viene gestita la creazione dell'albero sintattico decorato, ovvero l'aggiunta degli attributi 
     * all'albero in maniera automatica, tramite l'utilizzo di espressioni regolari.
     * Qui viene gestita la modalita' di gestione degli errori con diverse tipologie.
     * @return true se il file e' stato generato correttamente.
     */
    private boolean generaFileYacc() {
        
        File yaccFile;       //nuovo file che verra' creato
        FileWriter fw = null;
        
        //Copio la struttura del file nel nuovo file di input per yacc.
        if(ManipolaFile.copiaFile(filenameStampoYacc, yaccFilename,true)) {
            
            //Inizio la costruzione del nuovo file .y .
            yaccFile = new File(yaccFilename);
            try {
                fw = new FileWriter(yaccFile,false);  //apro e sostituisco il file.
                
                //PRIMA PARTE
                String line = "%{\nimport java.io.*;\nimport albero.*;\nimport java.util.HashMap;\nimport java.util.LinkedList;\nimport java.util.Vector;\nimport java.util.Iterator;\n%}\n\n";
                
                fw.write(line);
                fw.flush();
                
                //SECONDA PARTE: TOKENS
                String tokenType = null;
                Token tk = null;
                Iterator it = tokens.iterator();
                while(it.hasNext()) {
                    
                    tk = (Token)it.next();
                    
                    //Verifico di che tipo di  token si tratti
                    if(tk.getThisTokenType() != linguaggio.AttributeType.tokenType.niente && tk.getThisTokenType() != linguaggio.AttributeType.tokenType.ignora)
                        tokenType = "<"+tk.getThisTokenType().name()+">";
                    else
                        tokenType="";
                    
                    line = "\n%token "+tokenType+" "+tk.getName();
                    fw.write(line);
                    fw.flush();
                }
                
                fw.write("\n\n");
                fw.flush();
                
                //TERZA PARTE: TYPE
                
                Iterator itnt = nonTerminali.iterator();
                Produzione p = null;
                Vector<Produzione> vp = null;
                NonTerminale NonTerm = null;
                
                while(itnt.hasNext()) {

                    NonTerm = (NonTerminale)itnt.next();
                    
                    vp = (Vector) NonTerm.getProduzioni();
                    //p = (Produzione)vp.get(0);
                            
                    //line="\n%type\t<"+NonTerm.getThisTokenType().name()+">\t"+p.getParteSinistra();
                    if(NonTerm.getThisTokenType() != AttributeType.tokenType.niente) {
                        line="\n%type\t<"+NonTerm.getThisTokenType().name()+">\t"+NonTerm.getName();

                        //System.out.println(line);
                        fw.write(line);
                        fw.flush();
                    }
                }
                
                //QUARTA PARTE: PRECEDENZE
                fw.write("\n");
                fw.flush();
                
                
                    //PRECEDENZA SINISTRA
                Iterator itprecedenza = grammatica.getLeft().iterator();
                Token tokenPrecedenza = null;
                String precedenzaString ="";
                while(itprecedenza.hasNext()) {
                    tokenPrecedenza = (Token)itprecedenza.next();
                    precedenzaString+=tokenPrecedenza.getName()+" ";
                    
                }
                
                if(grammatica.getLeft().size()>0) {
                    fw.write("\n"+"%left "+precedenzaString);
                    fw.flush();
                }
                
                
                //PRECEDENZA DESTRA
                itprecedenza = grammatica.getRight().iterator();
                tokenPrecedenza = null;
                precedenzaString ="";
                while(itprecedenza.hasNext()) {
                    tokenPrecedenza = (Token)itprecedenza.next();
                    precedenzaString+=tokenPrecedenza.getName()+" ";
                    System.out.print("\n\n\n\nPRECEDENZA: "+tokenPrecedenza.getName());
                    
                }
                
                if(grammatica.getRight().size()>0) {
                    fw.write("\n"+"%right "+precedenzaString);
                    fw.flush();
                }
                
                //PRECEDENZA NOSNASSOC
                itprecedenza = grammatica.getNonassoc().iterator();
                tokenPrecedenza = null;
                precedenzaString ="";
                while(itprecedenza.hasNext()) {
                    tokenPrecedenza = (Token)itprecedenza.next();
                    precedenzaString+=tokenPrecedenza.getName()+" ";
                    
                }
                
                if(grammatica.getNonassoc().size()>0) {
                    fw.write("\n"+"%nonassoc "+precedenzaString);
                    fw.flush();
                }
                
                fw.write("\n%%\n");
                fw.flush();
                                
                
                                 
                
                //  QUINTA/SESTA PARTE: PRODUZIONI + PRODUZIONI ERRORE
                
                //Dapprima ottengo tutti i non terminali in un SET.
                
                itnt = nonTerminali.iterator();
                
                String lineTree = null;
                
                String lineASD = null;
                
                //Ricavo la parte sinistra, ea aggiungo (in OR |) la parte destra.
                while(itnt.hasNext()) {
                    
                    
                    NonTerm = (NonTerminale) itnt.next();
                    
                    vp = (Vector) NonTerm.getProduzioni();
                    
                    it = vp.iterator();
                    
                    
                    line = "\n "+NonTerm.getName()+" : \n\t\t";  //Parte sinistra della produzione
                    //A questo punto ho le produzioni di 1 non terminale
                    while(it.hasNext()) {
                        p = (Produzione)it.next();  
                        lineTree="";
                        lineASD = Utility.addNodeTree2(NonTerm,p,tokens);
                        if(lineASD!=null)
                            lineTree = lineASD;
                        
                        line += p.stampaParteDestra()+"\t\t{"+p.getRegolaSemantica()+"\n\t\t"+lineTree+"}"+" \n\t|\t";
                        
                    }
                    
                    //CONSIDERO EVENTUALI PRODUZIONI DI ERRORE DEL NON TERMINALE
                    if(this.grammatica.getErrorType() != Grammatica.ErrorType.nothing) {
                        Iterator iterrore = grammatica.getProduzioniErrore().iterator();
                        NonTerminale nonTermErrore = null;
                        Produzione pe = null;
                        while(iterrore.hasNext()) {
                            nonTermErrore = (NonTerminale) iterrore.next();
                            //verifico se si tratta della produzione di errore del non terminale che sto considerando.
                            if(NonTerm.getName().equals(nonTermErrore.getName())) {
                                Vector vpe = (Vector) nonTermErrore.getProduzioni();
                        
                                Iterator iterroreproduzione = vpe.iterator();

                                //A questo punto ho le produzioni di 1 non terminale
                                while(iterroreproduzione.hasNext()) {
                                    pe = (Produzione)iterroreproduzione.next();                        
                                    line += pe.stampaParteDestra()+"\t\t{"+pe.getRegolaSemantica()+"\n\t\t}"+" \n\t|\t";
                                }
                            }
                        }
                        
                    }
                    
                    //Rimuovo l'ultimo "|" , aggiungo ";" e scrivo su file
                    line = line.substring(0, line.length()-4);
                    line+="\n;";
                    fw.write(line);
                    fw.flush();
                }
                
                //SETTIMA PARTE: COPIA DELLA STRUTTURA DEL FILE YACC
                fw.write("\n\n");
                fw.flush();
                fw.close();
                
                //Concateno la parte di struttura yacc al file che ho creato.
                return ManipolaFile.copiaFile(filenameStampoYacc, yaccFilename, true);
                
            } 
            catch (IOException ex) {
                return false;
            }
            
        }
        
        return false;
    }
    
    
    /**
     * Genera il main che permette il parsing.
     * @return 
     */
    public boolean generaMain() {
        
        return ManipolaFile.copiaFile("config/Main.java", "src/generatoreLALR/Main.java", false);
    }
    

}
